﻿using System;
using System.Text;
using System.IO;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;

namespace UDS
{
    public delegate T ReadDataAction<T>() where T : class;

    /// <summary>
    /// This class provides some useful method for a web application.
    /// </summary>
    public static class HttpUtil
    {
        internal const string CachePrefix = "UDS_CACHE-";

        public static string HtmlEncode(string s, string s1)
        {
            if (string.IsNullOrEmpty(s))
            {
                return s1;
            }
            return HttpUtility.HtmlEncode(s);
        }

        public static string MapPath(string vPath)
        {
            return HostingEnvironment.MapPath(vPath);
        }

        public static string ResolveUrl(string vPath)
        {
            VirtualFile f = HostingEnvironment.VirtualPathProvider.GetFile(vPath);
            if (f != null)
            {
                return f.VirtualPath;
            }
            return vPath;
        }

        /// <summary>
        /// Converts quotation marks, including <b>'</b> and <b>""</b>, in a string
        /// into an HTML-encoded string. 
        /// </summary>
        public static string HtmlQuotationMarkEncode(string s)
        {
            return HtmlQuotationMarkEncode(s, false);
        }

        /// <summary>
        /// Converts quotation marks, including <b>'</b> and <b>""</b>, in a string
        /// into an HTML-encoded string. 
        /// </summary>
        public static string HtmlQuotationMarkEncode(string s, bool forScriptUse)
        {
            if (string.IsNullOrEmpty(s))
            {
                return s;
            }
            if (!forScriptUse)
            {
                return QuotationMarksEncodeForHtmlElements(s);
            }

            return QuotationMarkEncodeForScripts(s);
        }

        internal static string EnsureEndWithSemiColon(string value)
        {
            if (value != null)
            {
                int length = value.Length;
                if (length > 0 && value[length - 1] != ';')
                {
                    return value + ";";
                }
            }
            return value;
        }

        internal static string MergeScript(string first, string second)
        {
            first = EnsureEndWithSemiColon(first);
            if (!string.IsNullOrEmpty(first))
            {
                return first + second;
            }

            if (string.IsNullOrEmpty(second))
            {
                return first;
            }
            second = second.TrimStart(null);
            if (second.StartsWith("javascript:", StringComparison.Ordinal))
            {
                return second;
            }

            return "javascript:" + second;
        }

        internal static T ReadCache<T>(string cacheKey, ReadDataAction<T> callback, int cacheDuration)
            where T : class
        {
            T value = (T)HttpRuntime.Cache[cacheKey];
            if (value == null)
            {
                // Read the data directly and then insert it to cache.
                value = callback();
                if (value != null)
                {
                    HttpRuntime.Cache.Insert(cacheKey, value, null, DateTime.UtcNow.AddMinutes(cacheDuration), Cache.NoSlidingExpiration);
                }
            }

            return value;
        }

        internal static string GetNameOfPostedFile(HttpPostedFile postedFile)
        {
            if (postedFile != null
                && postedFile.ContentLength > 0
                && !string.IsNullOrEmpty(postedFile.FileName))
            {
                return Path.GetFileName(postedFile.FileName);
            }
            return string.Empty;
        }

        /// <summary>
        /// Delete an file under "~/Files" directory
        /// </summary>
        internal static void DeleteAttachFile(string fileVirtualPath, bool removeParentDirectory)
        {
            string rootFileDir = HttpUtil.MapPath("~/Files/");
            string physicalFile = HttpUtil.MapPath(fileVirtualPath);
            FileInfo fileInfo = new FileInfo(physicalFile);

            if (!fileInfo.FullName.StartsWith(rootFileDir, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new InvalidOperationException(string.Format("Cannot remove file '{0}' since it was not stored under '~/Files' directory.", fileVirtualPath));
            }

            if (fileInfo.Exists)
            {
                fileInfo.Delete();
            }

            // Also remove the parent dictionary
            DirectoryInfo dirInfo = fileInfo.Directory;
            if (dirInfo.Exists)
            {
                // Is the directory empty?
                FileSystemInfo[] array = dirInfo.GetFileSystemInfos();
                bool empty = true;
                if (array != null)
                {
                    foreach (var fsInfo in array)
                    {
                        // A directory will be considered as empty if it does not have sub-directories and non-hidden files.
                        if ((fsInfo.Attributes & FileAttributes.Directory) != 0)
                        {
                            empty = false;
                            break;
                        }
                        if ((fsInfo.Attributes & FileAttributes.Hidden) == 0)
                        {
                            // The object is not hidden.
                            empty = false;
                            break;
                        }
                    }
                }

                if (empty)
                {
                    // Remove this empty directory
                    dirInfo.Delete();
                }
            }
        }

        private static string QuotationMarkEncodeForScripts(string s)
        {
            char[] charArray = s.ToCharArray();
            int length = charArray.Length;
            var encodedCharArray = new char[length * 2];
            int index = 0;

            for (int i = 0; i < length; i++)
            {
                char ch = charArray[i];
                switch (ch)
                {
                    case '\'':
                    case '"':
                    case '\\':
                        encodedCharArray[index++] = '\\';
                        encodedCharArray[index++] = ch;
                        break;

                    case '\n':
                        encodedCharArray[index++] = '\\';
                        encodedCharArray[index++] = 'n';
                        break;

                    case '\r':
                        break;

                    default:
                        encodedCharArray[index++] = ch;
                        break;
                }
            }

            return new string(encodedCharArray, 0, index);
        }

        private static string QuotationMarksEncodeForHtmlElements(string s)
        {
            // Tries to find a quotation mark in the specified string
            int questionMarkPos = IndexOfQuotationMark(s, 0);
            if (questionMarkPos < 0)
            {
                // There is no quotation marks
                return s;
            }

            var stringBuilder = new StringBuilder();
            int stringLength = s.Length;
            int currentPos = 0;

            while (true)
            {
                if (questionMarkPos > currentPos)
                {
                    stringBuilder.Append(s, currentPos, questionMarkPos - currentPos);
                }

                // Converts a quotation mark into html entities.
                char questionMarker = s[questionMarkPos];
                if (questionMarker == '\'')
                {
                    stringBuilder.Append("&#39;");
                }
                else
                {
                    stringBuilder.Append("&quot;");
                }

                currentPos = questionMarkPos + 1;
                if (currentPos < stringLength)
                {
                    questionMarkPos = IndexOfQuotationMark(s, currentPos);
                    if (questionMarkPos < 0)
                    {
                        stringBuilder.Append(s, currentPos, stringLength - currentPos);
                        break;
                    }
                }
                else
                {
                    break;
                }
            }

            return stringBuilder.ToString();
        }

        private static unsafe int IndexOfQuotationMark(string s, int offset)
        {
            int lengthToCheck = s.Length - offset;
            fixed (char* fixedCharPtr = s)
            {
                char* chPtr = fixedCharPtr + offset;

                while (lengthToCheck > 0)
                {
                    char ch = chPtr[0];
                    if (ch == '\'' || ch == '\"')
                    {
                        return s.Length - lengthToCheck;
                    }

                    chPtr++;
                    lengthToCheck--;
                }
            }

            return -1;
        }
    }
}